package com.easypan.service.impl;

import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;

import javax.annotation.Resource;

import com.easypan.component.RedisComponent;
import com.easypan.entity.config.AppConfig;
import com.easypan.entity.constants.Constants;
import com.easypan.entity.dto.SessionWebUserDto;
import com.easypan.entity.dto.UploadResultDto;
import com.easypan.entity.dto.UserSpaceDto;
import com.easypan.entity.enums.*;
import com.easypan.entity.po.UserInfo;
import com.easypan.entity.query.UserInfoQuery;
import com.easypan.exception.BusinessException;
import com.easypan.mappers.UserInfoMapper;
import com.easypan.utils.DateUtil;
import com.easypan.utils.ProcessUtils;
import com.easypan.utils.ScaleFilter;
import org.apache.commons.io.FileUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.Lazy;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;

import com.easypan.entity.query.FileInfoQuery;
import com.easypan.entity.po.FileInfo;
import com.easypan.entity.vo.PaginationResultVO;
import com.easypan.entity.query.SimplePage;
import com.easypan.mappers.FileInfoMapper;
import com.easypan.service.FileInfoService;
import com.easypan.utils.StringTools;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.support.TransactionSynchronization;
import org.springframework.transaction.support.TransactionSynchronizationManager;
import org.springframework.web.multipart.MultipartFile;


/**
 * 文件信息 业务接口实现
 */
@Service("fileInfoService")
public class FileInfoServiceImpl implements FileInfoService {

	private static final Logger logger = LoggerFactory.getLogger(FileInfoServiceImpl.class);

	@Resource
	private RedisComponent redisComponent;

	@Resource
	private FileInfoMapper<FileInfo, FileInfoQuery> fileInfoMapper;

	@Resource
	private UserInfoMapper<UserInfo, UserInfoQuery> userInfoMapper;

	@Resource
	private AppConfig appConfig;

	@Resource
	@Lazy  // 这里要加这个注解，否则自己掉自己会产生循环依赖
	private FileInfoServiceImpl fileInfoService;

	/**
	 * 根据条件查询列表
	 */
	@Override
	public List<FileInfo> findListByParam(FileInfoQuery param) {
		return this.fileInfoMapper.selectList(param);
	}

	/**
	 * 根据条件查询列表
	 */
	@Override
	public Integer findCountByParam(FileInfoQuery param) {
		return this.fileInfoMapper.selectCount(param);
	}

	/**
	 * 分页查询方法
	 */
	@Override
	public PaginationResultVO<FileInfo> findListByPage(FileInfoQuery param) {
		int count = this.findCountByParam(param);
		int pageSize = param.getPageSize() == null ? PageSize.SIZE15.getSize() : param.getPageSize();

		SimplePage page = new SimplePage(param.getPageNo(), count, pageSize);
		param.setSimplePage(page);
		List<FileInfo> list = this.findListByParam(param);
		PaginationResultVO<FileInfo> result = new PaginationResultVO(count, page.getPageSize(), page.getPageNo(), page.getPageTotal(), list);
		return result;
	}

	/**
	 * 新增
	 */
	@Override
	public Integer add(FileInfo bean) {
		return this.fileInfoMapper.insert(bean);
	}

	/**
	 * 批量新增
	 */
	@Override
	public Integer addBatch(List<FileInfo> listBean) {
		if (listBean == null || listBean.isEmpty()) {
			return 0;
		}
		return this.fileInfoMapper.insertBatch(listBean);
	}

	/**
	 * 批量新增或者修改
	 */
	@Override
	public Integer addOrUpdateBatch(List<FileInfo> listBean) {
		if (listBean == null || listBean.isEmpty()) {
			return 0;
		}
		return this.fileInfoMapper.insertOrUpdateBatch(listBean);
	}

	/**
	 * 多条件更新
	 */
	@Override
	public Integer updateByParam(FileInfo bean, FileInfoQuery param) {
		StringTools.checkParam(param);
		return this.fileInfoMapper.updateByParam(bean, param);
	}

	/**
	 * 多条件删除
	 */
	@Override
	public Integer deleteByParam(FileInfoQuery param) {
		StringTools.checkParam(param);
		return this.fileInfoMapper.deleteByParam(param);
	}

	/**
	 * 根据FileIdAndUserId获取对象
	 */
	@Override
	public FileInfo getFileInfoByFileIdAndUserId(String fileId, String userId) {
		return this.fileInfoMapper.selectByFileIdAndUserId(fileId, userId);
	}

	/**
	 * 根据FileIdAndUserId修改
	 */
	@Override
	public Integer updateFileInfoByFileIdAndUserId(FileInfo bean, String fileId, String userId) {
		return this.fileInfoMapper.updateByFileIdAndUserId(bean, fileId, userId);
	}

	/**
	 * 根据FileIdAndUserId删除
	 */
	@Override
	public Integer deleteFileInfoByFileIdAndUserId(String fileId, String userId) {
		return this.fileInfoMapper.deleteByFileIdAndUserId(fileId, userId);
	}

	@Override
	@Transactional(rollbackFor = Exception.class)
	public UploadResultDto uploadFile(SessionWebUserDto webUserDto, String fileId, MultipartFile file, String fileName, String filePid, String fileMd5, Integer chunkIndex, Integer chunks) {

		UploadResultDto resultDto = new UploadResultDto();
		Boolean uploadSuccess = true;
		File tempFileFolder = null;
		try{
			if (StringTools.isEmpty(fileId)) {
				fileId = StringTools.getRandomString(Constants.LENGTH_10);
			}
			resultDto.setFileId(fileId);
			Date curDate = new Date();
			// 用户当前已使用空间
			UserSpaceDto spaceDto = redisComponent.getUserSpaceUse(webUserDto.getUserId());

			// 若等于0就是第一个分片，去数据库中查一下MD5，如果存在后面的分片就不需要传了，直接秒传
			if (chunkIndex == 0) {
				// 去查转码成功的第一页的MD5值的文件
				FileInfoQuery infoQuery = new FileInfoQuery();
				infoQuery.setFileMd5(fileMd5);
				// 盲猜这面SimplePage(0,1)是取上传文件MD5值的第一个分页
				infoQuery.setSimplePage(new SimplePage(0,1));
				// 设置状态：
				infoQuery.setStatus(FileStatusEnums.TRANSFER_SUCCESS.getStatus());
				// 通过第一片可以在数据库中查到更完整的文件MD5值
				List<FileInfo> dbFileList = this.fileInfoMapper.selectList(infoQuery);
				// 通过上面的MD5值在数据库中查到了，就秒传
				if (!dbFileList.isEmpty()){
					FileInfo dbFile = dbFileList.get(0);
					// 判断文件大小
					if (dbFile.getFileSize() + spaceDto.getUseSpace() > spaceDto.getTotalSpace()) {
						throw new BusinessException(ResponseCodeEnum.CODE_904);
					}
					dbFile.setFileId(fileId);
					dbFile.setFilePid(filePid);
					dbFile.setUserId(webUserDto.getUserId());
					dbFile.setCreateTime(curDate);
					dbFile.setLastUpdateTime(curDate);
					dbFile.setStatus(FileStatusEnums.TRANSFER_SUCCESS.getStatus());
					dbFile.setDelFlag(FileDelFlagEnums.USING.getFlag());
					dbFile.setFileMd5(fileMd5);
					// 文件重命名
					fileName = autoRename(filePid, webUserDto.getUserId(), fileName);
					dbFile.setFileName(fileName);
					// 插入数据库
					this.fileInfoMapper.insert(dbFile);
					resultDto.setStatus(UploadStatusEnums.UPLOAD_SECONDS.getCode());
					// 更新用户使用空间
					updateUserSpace(webUserDto, dbFile.getFileSize());
					return resultDto;
				}
			}
			// 判断磁盘空间
			Long currentTempSize = redisComponent.getFileTempSize(webUserDto.getUserId(), fileId);
			if (file.getSize() + currentTempSize + spaceDto.getUseSpace() > spaceDto.getTotalSpace()) {
				throw new BusinessException(ResponseCodeEnum.CODE_904);
			}
			// 暂存临时目录
			String tempFolderName = appConfig.getProjectFolder() + Constants.FILE_FOLDER_TEMP;
			String currentUserFolderName = webUserDto.getUserId() + fileId;

			tempFileFolder = new File(tempFolderName + currentUserFolderName);
			if (!tempFileFolder.exists()) {
				tempFileFolder.mkdirs();
			}

			File newFile = new File(tempFileFolder.getPath() + "/" + chunkIndex);
			file.transferTo(newFile);
			// 保存临时大小
			redisComponent.saveFileTempSize(webUserDto.getUserId(), fileId, file.getSize());
			if (chunkIndex < chunks - 1) {
				resultDto.setStatus(UploadStatusEnums.UPLOADING.getCode());
				return resultDto;
			}
			// 最后一个分片上传完成记录数据库，异步合并分片
			String month = DateUtil.format(new Date(), DateTimePatternEnum.YYYYMM.getPattern());
			String fileSuffix = StringTools.getFileSuffix(fileName);
			// 真实的文件名
			String realFileName = currentUserFolderName + fileSuffix;
			FileTypeEnums fileTypeEnum = FileTypeEnums.getFileTypeBySuffix(fileSuffix);
			// 自动重命名
			fileName = autoRename(filePid, webUserDto.getUserId(), fileName);
			// 写数据库
			FileInfo fileInfo = new FileInfo();
			fileInfo.setFileId(fileId);
			fileInfo.setUserId(webUserDto.getUserId());
			fileInfo.setFileMd5(fileMd5);
			fileInfo.setFileName(fileName);
			fileInfo.setFilePath(month + "/" + realFileName);
			fileInfo.setFilePid(filePid);
			fileInfo.setCreateTime(curDate);
			fileInfo.setLastUpdateTime(curDate);
			fileInfo.setFileCategory(fileTypeEnum.getCategory().getCategory());
			fileInfo.setFileType(fileTypeEnum.getType());
			fileInfo.setStatus(FileStatusEnums.TRANSFERING.getStatus());
			fileInfo.setFolderType(FileFolderTypeEnums.FILE.getType());
			fileInfo.setDelFlag(FileDelFlagEnums.USING.getFlag());
			this.fileInfoMapper.insert(fileInfo);
			Long totalSize = redisComponent.getFileTempSize(webUserDto.getUserId(), fileId);
			// 更新使用空间
			updateUserSpace(webUserDto, totalSize);
			resultDto.setStatus(UploadStatusEnums.UPLOAD_FINISH.getCode());
			// 如果事务没有提交就做这个异步操作会有问题，所以要等事务提交后再做这个操作
			TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() {
				@Override
				public void afterCommit() {
					// 直接调用不可以原因：这里的真实原因不是没有交给spring管理，而是@Async是aop做拦截，最终调用的仍然是目标对象，
					// 而不是代码对象，因此这里要注入代理对象. 这样调用异步才是有效的
					fileInfoService.transferFile(fileInfo.getFileId(), webUserDto);
				}
			});
			return resultDto;
		} catch(BusinessException e) {
			logger.error("文件上传失败", e);
			uploadSuccess = false;
			throw e;
		} catch(Exception e) {
			logger.error("文件上传失败", e);
			uploadSuccess = false;
		} finally {
			if (!uploadSuccess && tempFileFolder != null) {
				try {
					FileUtils.deleteDirectory(tempFileFolder);
				} catch (IOException e) {
					logger.error("删除临时目录失败", e);
				}
			}
		}
		return resultDto;
	}


    private String autoRename(String filePid, String userId, String fileName) {
		FileInfoQuery fileInfoQuery = new FileInfoQuery();
		fileInfoQuery.setUserId(userId);
		fileInfoQuery.setFilePid(filePid);
		fileInfoQuery.setDelFlag(FileDelFlagEnums.USING.getFlag());
		fileInfoQuery.setFileName(fileName);
		Integer count = this.fileInfoMapper.selectCount(fileInfoQuery);
		if (count > 0) {
			fileName = StringTools.rename(fileName);
		}
		return fileName;
	}

	private void updateUserSpace(SessionWebUserDto webUserDto, Long useSpace) {
		Integer count = userInfoMapper.updateUserSpace(webUserDto.getUserId(), useSpace, null);
		if (count == 0) {
			throw new BusinessException(ResponseCodeEnum.CODE_904);
		}
		UserSpaceDto userSpaceDto = redisComponent.getUserSpaceUse(webUserDto.getUserId());
		userSpaceDto.setUseSpace(userSpaceDto.getUseSpace() + useSpace);
		redisComponent.saveUserSpaceUse(webUserDto.getUserId(), userSpaceDto);
	}

	@Async
	public void transferFile(String fileId, SessionWebUserDto webUserDto) {
		Boolean transferSuccess = true;
		String targetFilePath = null;
		String cover = null;
		FileTypeEnums fileTypeEnum = null;
		FileInfo fileInfo = this.fileInfoMapper.selectByFileIdAndUserId(fileId, webUserDto.getUserId());
		try {
			// 如果这个文件不存在或则不是转码中，就不处理他
			if (fileInfo == null || !FileStatusEnums.TRANSFERING.getStatus().equals(fileInfo.getStatus())) {
				return;
			}
			// 临时目录
			String tempFolderName = appConfig.getProjectFolder() + Constants.FILE_FOLDER_TEMP;
			String currentUserFolderName = webUserDto.getUserId() + fileId;
			File fileFolder = new File(tempFolderName  + currentUserFolderName);

			String fileSuffix = StringTools.getFileSuffix(fileInfo.getFileName());
			String month = DateUtil.format(fileInfo.getCreateTime(), DateTimePatternEnum.YYYYMM.getPattern());
			// 目标目录
			String targetFolderName = appConfig.getProjectFolder() + Constants.FILE_FOLDER_FILE;
			File targetFolder = new File(targetFolderName + "/" + month);
			if (!targetFolder.exists()) {
				targetFolder.mkdirs();
			}

			// 真实的文件名
			String realFileName = currentUserFolderName + fileSuffix;
			targetFilePath = targetFolder.getPath() + "/" + realFileName;
			// 合并文件
			union(fileFolder.getPath(), targetFilePath, fileInfo.getFileName(), true);
			// 视频文件切割
			fileTypeEnum = FileTypeEnums.getFileTypeBySuffix(fileSuffix);
			if (FileTypeEnums.VIDEO == fileTypeEnum) {
				cutFile4Video(fileId, targetFilePath);
				// 视频生成缩略图
				cover = month + "/" + currentUserFolderName + Constants.IMAGE_PNG_SUFFIX;
				String coverPath = targetFolderName + "/" + cover;
				ScaleFilter.createCover4Video(new File(targetFilePath), Constants.LENGTH_150, new File(coverPath));

			} else if (FileTypeEnums.IMAGE == fileTypeEnum) {
				// 生成缩略图
				cover = month + "/" + realFileName.replace(".", "_.");
				String coverPath  = targetFolderName + "/" + cover;
				Boolean created = ScaleFilter.createThumbnailWidthFFmpeg(new File(targetFilePath), Constants.LENGTH_150, new File(coverPath), false);
				if (!created) {
					// 如果没有生成缩略图，说明你的原图太小了，直接返回了false，所以直接copy一份作为缩略图
					FileUtils.copyFile(new File(targetFilePath), new File(coverPath));
				}
			}
		} catch (Exception e) {
			logger.error("文件转码失败，文件ID:{},userID:{}",fileId, webUserDto.getUserId(), e);
			transferSuccess = false;
		} finally {
			FileInfo updateInfo = new FileInfo();
			updateInfo.setFileSize(new File(targetFilePath).length());
			updateInfo.setFileCover(cover);
			updateInfo.setStatus(transferSuccess ? FileStatusEnums.TRANSFER_SUCCESS.getStatus() : FileStatusEnums.TRANSGER_FAIL.getStatus());
			fileInfoMapper.updateFileStatusWithOldStatus(fileId, webUserDto.getUserId(), updateInfo, FileStatusEnums.TRANSFERING.getStatus());
		}

	}

	private void union(String dirPath, String toFilePath, String fileName, Boolean delSource) {
		File dir = new File(dirPath);
		if (!dir.exists()) {
			throw new BusinessException("目录不存在");
		}

		File[] fileList = dir.listFiles();
		File targetFile = new File(toFilePath);
		RandomAccessFile writeFile = null;
		try {
			writeFile = new RandomAccessFile(targetFile, "rw");
			byte[] b = new byte[1024 * 10];
			for (int i = 0; i < fileList.length; i++) {
				int len = -1;
				File chunkFile = new File(dirPath + "/" + i);
				RandomAccessFile readFile = null;
				try {
					readFile = new RandomAccessFile(chunkFile, "r");
					while ((len = readFile.read(b)) != -1) {
						writeFile.write(b,0,len);
					}
				} catch (Exception e) {
					logger.error("合并分片失败", e);
					throw new BusinessException("合并分片失败");
				} finally {
					readFile.close();
				}
			}
		} catch (Exception e) {
			logger.error("合并文件:{}失败", fileName, e);
			throw new BusinessException("合并文件" + fileName + "出错");
		} finally {
			if (writeFile != null) {
				try {
					writeFile.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
			if (delSource && dir.exists()) {
				try {
					FileUtils.deleteDirectory(dir);
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
		}
	}

	private void  cutFile4Video(String fileId, String videoFilePath) {
		// 创建同名切片目录
		File tsFolder = new File(videoFilePath.substring(0, videoFilePath.lastIndexOf(".")));
		if (!tsFolder.exists()) {
			tsFolder.mkdirs();
		}
		final String CMD_TRANSFER_2TS = "ffmpeg -y -i %s -vcodec copy -acodec copy -vbsf h264_mp4toannexb %s";
		final String CMD_CUT_TS = "ffmpeg -i %s -c copy -map 0 -f segment -segment_list %s -segment_time 5 %s/%s_%%4d.ts";
		String tsPath = tsFolder + "/" + Constants.TS_NAME;
		// 生成T.ts
		String cmd = String.format(CMD_TRANSFER_2TS, videoFilePath, tsPath);
		ProcessUtils.executeCommand(cmd, false);
		// 生成索引文件.m3u8和切片.ts文件
		cmd = String.format(CMD_CUT_TS, tsPath, tsFolder.getPath() + "/"
				+ Constants.M3U8_NAME, tsFolder.getPath(), fileId);
		ProcessUtils.executeCommand(cmd, false);
		// 删除index.ts文件
		new File(tsPath).delete();
	}

	@Override
	public FileInfo newFolder(String filePid, String userId, String folderName) {
		checkFileName(filePid,userId,folderName,FileFolderTypeEnums.FOLDER.getType());
		Date curDate = new Date();
		FileInfo fileInfo = new FileInfo();
		fileInfo.setFileId(StringTools.getRandomString(Constants.LENGTH_10));
		fileInfo.setUserId(userId);
		fileInfo.setFilePid(filePid);
		fileInfo.setFileName(folderName);
		fileInfo.setFolderType(FileFolderTypeEnums.FOLDER.getType());
		fileInfo.setCreateTime(curDate);
		fileInfo.setLastUpdateTime(curDate);
		fileInfo.setStatus(FileStatusEnums.TRANSFER_SUCCESS.getStatus());
		fileInfo.setDelFlag(FileDelFlagEnums.USING.getFlag());
		this.fileInfoMapper.insert(fileInfo);
		return fileInfo;
	}


    /**
	 * 校验是否存在同名文件
	 * @param filePid
	 * @param userId
	 * @param fileName
	 * @param folderType
	 */
	private void checkFileName(String filePid, String userId, String fileName, Integer folderType) {
		FileInfoQuery fileInfoQuery = new FileInfoQuery();
		fileInfoQuery.setFolderType(folderType);
		fileInfoQuery.setFileName(fileName);
		fileInfoQuery.setFilePid(filePid);
		fileInfoQuery.setUserId(userId);
		fileInfoQuery.setDelFlag(FileDelFlagEnums.USING.getFlag());
		Integer count = this.fileInfoMapper.selectCount(fileInfoQuery);
		if (count > 0) {
			throw new BusinessException("此目录下已经存在同名文件，请修改名称。");
		}
	}

	/**
	 * 这里面查询数据库和存数据库的操作与视频不一致，但是视频没问题
	 * 因为这个方法上加了事务，如果抛了异常会回滚
	 * @param fileId
	 * @param userId
	 * @param fileName
	 * @return
	 */
	@Override
	@Transactional(rollbackFor = Exception.class)
	public FileInfo rename(String fileId, String userId, String fileName) {
		FileInfo fileInfo = this.fileInfoMapper.selectByFileIdAndUserId(fileId, userId);
		if (fileInfo == null) {
			throw new BusinessException("文件不存在");
		}
		String filePid = fileInfo.getFilePid();
		checkFileName(filePid,userId,fileName,fileInfo.getFolderType());
		// 获取文件后缀
		if (FileFolderTypeEnums.FILE.getType().equals(fileInfo.getFolderType())) {
			// 为什么这么做：因为重名的时候，前端设置文件名的后缀不可以修改
			fileName = fileName + StringTools.getFileSuffix(fileInfo.getFileName());
		}
		Date curDate = new Date();
		// 先查一下数据库中有没有这个文件名，如果有就抛个异常
		FileInfoQuery fileInfoQuery = new FileInfoQuery();
		fileInfoQuery.setFilePid(filePid);
		fileInfoQuery.setUserId(userId);
		fileInfoQuery.setFileName(fileName);
		fileInfoQuery.setDelFlag(FileDelFlagEnums.USING.getFlag());
		Integer count = this.fileInfoMapper.selectCount(fileInfoQuery);
		if (count > 1) {
			throw new BusinessException("文件名" + fileName + "已经存在");
		}
		// 如果没有就存入数据库
		FileInfo dbInfo = new FileInfo();
		dbInfo.setFileName(fileName);
		dbInfo.setLastUpdateTime(curDate);
		this.fileInfoMapper.updateByFileIdAndUserId(dbInfo,fileId,userId);

		fileInfo.setFileName(fileName);
		fileInfo.setLastUpdateTime(curDate);
		return fileInfo;
	}

    @Override
    public void changeFileFolder(String fileIds, String filePid, String userId) {
        // 如果当前文件已经在当前目录，还往这里移动，没意义
		if (fileIds.equals(filePid)) {
        	throw new BusinessException(ResponseCodeEnum.CODE_600);
		}
		// 如果当前文件的 filePid 不是0, 就是当前文件有父级目录(即当前文件不在根目录下)
		if (!Constants.ZERO_STR.equals(filePid)) {
			FileInfo fileInfo = fileInfoService.getFileInfoByFileIdAndUserId(filePid, userId);
			if (fileInfo == null || !FileDelFlagEnums.USING.getFlag().equals(fileInfo.getDelFlag())) {
				throw new BusinessException(ResponseCodeEnum.CODE_600);
			}
		}
		// 选中的，即要批量移动的文件
		String[] fileIdArray = fileIds.split(",");
		FileInfoQuery query = new FileInfoQuery();
		// 根据用户id和每个文件的父级目录可以查到要移动到的文件夹下的所有文件
		query.setFilePid(filePid);
		query.setUserId(userId);
		List<FileInfo> dbFileList = this.findListByParam(query);

		// 如果你要移动到的目录下与你移动的文件重名要修改文件名
		// 为了防止多次循环，我们把List转成Map，通过文件名作为Key，文件作为Value,出现同名取后一个
		Map<String, FileInfo> dbFileNameMap = dbFileList.stream().collect(Collectors.toMap(FileInfo::getFileName, Function.identity(), (data1, data2) -> data2));
		// 查询选中的文件
		query = new FileInfoQuery();
		query.setUserId(userId);
		query.setFileIdArray(fileIdArray);
		List<FileInfo> selectFileList =  this.findListByParam(query);
		// 将所选文件重命名
		for (FileInfo item : selectFileList) {
			FileInfo rootFileInfo = dbFileNameMap.get(item.getFileName());
			// 文件名已经存在，重命名被还原的文件名
			FileInfo updateInfo = new FileInfo();
			if (rootFileInfo != null) {
				String fileName = StringTools.rename(item.getFileName());
				updateInfo.setFileName(fileName);
			}
			updateInfo.setFilePid(filePid);
			this.fileInfoMapper.updateByFileIdAndUserId(updateInfo, item.getFileId(), userId);
		}
	}

	/**
	 * 到时候回收站中只能看见删除了的最外层目录，所以在标记时最外层标记为 RECYCLE(1,"回收站"),
	 * 而它里面的无论是文件还是文件夹在回收站都是看不见的，所以都标记为 DEL(0,"删除")。
	 * @param userId
	 * @param fileIds
	 */
	@Override
	public void removeFile2RecycleBatch(String userId, String fileIds) {
		String[] fileIdArray = fileIds.split(",");
		FileInfoQuery query = new FileInfoQuery();
		query.setUserId(userId);
		query.setFileIdArray(fileIdArray);
		query.setDelFlag(FileDelFlagEnums.USING.getFlag());
		List<FileInfo> fileInfoList = this.fileInfoMapper.selectList(query);
		if (fileInfoList.isEmpty()) {
			return;
		}
		// 要删除目录下包含本身和其他所有目录列表
		List<String> delFilePidList = new ArrayList<>();
		for (FileInfo fileInfo : fileInfoList) {
			findAllSubFolderFileList(delFilePidList, userId, fileInfo.getFileId(), FileDelFlagEnums.USING.getFlag());
		}
		if (!delFilePidList.isEmpty()) {
			FileInfo updateInfo = new FileInfo();
			updateInfo.setDelFlag(FileDelFlagEnums.DEL.getFlag());
			this.fileInfoMapper.updateFileDelFlagBatch(updateInfo, userId, delFilePidList,null, FileDelFlagEnums.USING.getFlag());
		}

		// 将选中的文件更新为回收站
		List<String> delFileIdList = Arrays.asList(fileIdArray);
		FileInfo fileInfo = new FileInfo();
		fileInfo.setRecoveryTime(new Date());
		fileInfo.setDelFlag(FileDelFlagEnums.RECYCLE.getFlag());
		this.fileInfoMapper.updateFileDelFlagBatch(fileInfo, userId, null, delFileIdList, FileDelFlagEnums.USING.getFlag());


	}


    /**
	 * 拿到某一目录下的所有目录，并把他们放到列表 List<String> fileIdList 中
	 * @param fileIdList
	 * @param userId
	 * @param fileId
	 * @param delFlag
	 */
	private void  findAllSubFolderFileList(List<String> fileIdList, String userId, String fileId, Integer delFlag) {
		// 先把要删除的父级目录加入列表，然后挨个把其中的目录加入到这个列表中
		fileIdList.add(fileId);
		FileInfoQuery query = new FileInfoQuery();
		query.setUserId(userId);
		query.setFilePid(fileId);
		query.setDelFlag(delFlag);
		query.setFolderType(FileFolderTypeEnums.FOLDER.getType());
		List<FileInfo> fileInfoList = this.fileInfoMapper.selectList(query);
		for (FileInfo fileInfo : fileInfoList) {
			findAllSubFolderFileList(fileIdList, userId, fileInfo.getFileId(), delFlag);
		}
	}

	@Override
	@Transactional(rollbackFor = Exception.class)
	public void recoveryFileBatch(String userId, String fileIds) {
		String[] fileIdArray = fileIds.split(",");
		FileInfoQuery query = new FileInfoQuery();
		query.setUserId(userId);
		query.setFileIdArray(fileIdArray);
		query.setDelFlag(FileDelFlagEnums.RECYCLE.getFlag());
		List<FileInfo> fileInfoList = this.fileInfoMapper.selectList(query);
		List<String> delFileSubFolderFileIdList = new ArrayList<>();
		for (FileInfo fileInfo : fileInfoList) {
			if (FileFolderTypeEnums.FOLDER.getType().equals(fileInfo.getFolderType())) {
				// 把所有标记为删除的文件夹放到delFileSubFolderFileIdList里
				findAllSubFolderFileList(delFileSubFolderFileIdList, userId, fileInfo.getFileId(), FileDelFlagEnums.DEL.getFlag());
			}
		}
		// 查询所有根目录的文件(因为要还原到根目录，所以要首先查询根目录下的所有文件，不能同名，所以还原时会有重命名操作）
		query = new FileInfoQuery();
		query.setUserId(userId);
		query.setDelFlag(FileDelFlagEnums.USING.getFlag());
		query.setFilePid(Constants.ZERO_STR);
		List<FileInfo> allRootFileList = this.findListByParam(query);

		Map<String, FileInfo> rootFileMap = allRootFileList.stream().collect(Collectors.toMap(FileInfo::getFileName, Function.identity(), (data1,data2) -> data2));
		// 查询所有所选文件 将 目录下的所有删除的文件更新为使用中
		if (!delFileSubFolderFileIdList.isEmpty()) {
			FileInfo fileInfo = new FileInfo();
			fileInfo.setDelFlag(FileDelFlagEnums.USING.getFlag());
			// 自己加的，因为还原时相当于对文件操作，所以要更新操作时间，不加也符合逻辑
//			fileInfo.setLastUpdateTime(new Date());
			this.fileInfoMapper.updateFileDelFlagBatch(fileInfo, userId, delFileSubFolderFileIdList, null, FileDelFlagEnums.DEL.getFlag());
		}
		// 将所选中文件也更新为正常，且父级目录到根目录(就是回收站中看的见的，即标记为RECOVERY的文件)
		List<String> delFileIdList = Arrays.asList(fileIdArray);
		FileInfo fileInfo = new FileInfo();
		fileInfo.setDelFlag(FileDelFlagEnums.USING.getFlag());
		fileInfo.setFilePid(Constants.ZERO_STR);
		fileInfo.setLastUpdateTime(new Date());
		this.fileInfoMapper.updateFileDelFlagBatch(fileInfo, userId, null, delFileIdList, FileDelFlagEnums.RECYCLE.getFlag());

		// 将所选文件重命名
		for (FileInfo item : fileInfoList) {
			FileInfo rootFileInfo = rootFileMap.get(item.getFileName());
			// 文件已存在，重命名被还原的文件名
			if (rootFileInfo != null) {
				String fileName = StringTools.rename(item.getFileName());
				FileInfo updateInfo = new FileInfo();
				updateInfo.setFileName(fileName);
				this.fileInfoMapper.updateByFileIdAndUserId(updateInfo, item.getFileId(), userId);
			}
		}
	}

	@Override
	@Transactional(rollbackFor = Exception.class)
	public void delFileBatch(String userId, String fileIds, Boolean adminOp) {
		String[] fileIdArray = fileIds.split(",");
		FileInfoQuery query = new FileInfoQuery();
		query.setUserId(userId);
		query.setFileIdArray(fileIdArray);
		query.setDelFlag(FileDelFlagEnums.RECYCLE.getFlag());
		List<FileInfo> fileInfoList = this.fileInfoMapper.selectList(query);

		List<String> delFileSubFileFolderFileIdList = new ArrayList<>();
		// 找到所选文件子目录文件ID
		for (FileInfo fileInfo : fileInfoList) {
			if (FileFolderTypeEnums.FOLDER.getType().equals(fileInfo.getFolderType())) {
				// 获取包含父级目录及其下的子目录，并放入 delFileSubFileFolderFileIdList
				findAllSubFolderFileList(delFileSubFileFolderFileIdList, userId, fileInfo.getFileId(), FileDelFlagEnums.DEL.getFlag());
			}
		}

		// 删除所选文件子目录中的文件，标记为DEL的
		/**
		 * 按照 filePid 去数据库中删除的
		 */
		if (!delFileSubFileFolderFileIdList.isEmpty()) {
			this.fileInfoMapper.delFileBatch(userId, delFileSubFileFolderFileIdList, null, adminOp ? null : FileDelFlagEnums.DEL.getFlag());
		}
		// 删除所选文件(回收站中的)，标记为RECYCLE的
		this.fileInfoMapper.delFileBatch(userId, null, Arrays.asList(fileIdArray), adminOp ? null : FileDelFlagEnums.RECYCLE.getFlag());

		// 退回空间
		// 数据库中更新使用空间
		Long useSpace = this.fileInfoMapper.selectUseSpace(userId);
		UserInfo userInfo = new UserInfo();
		userInfo.setUseSpace(useSpace);
		this.userInfoMapper.updateByUserId(userInfo, userId);

		// redis----设置缓存
		UserSpaceDto userSpaceDto = redisComponent.getUserSpaceUse(userId);
		userSpaceDto.setUseSpace(useSpace);
		redisComponent.saveUserSpaceUse(userId, userSpaceDto);
	}

    @Override
    public void checkRootFilePid(String rootFilePid, String userId, String fileId) {
        if (StringTools.isEmpty(fileId)) {
        	throw new BusinessException(ResponseCodeEnum.CODE_600);
		}
        if (rootFilePid.equals(fileId)) {
        	return;
		}
		checkFilePid(rootFilePid, fileId, userId);
    }

    /**
	 *
	 */
	private void checkFilePid(String rootFilePid, String fileId, String userId) {
		FileInfo fileInfo = this.fileInfoMapper.selectByFileIdAndUserId(fileId, userId);
		// 不走界面
		if (fileInfo == null) {
			throw new BusinessException(ResponseCodeEnum.CODE_600);
		}
		// todo:根目录
		if (Constants.ZERO_STR.equals(fileInfo.getFilePid())) {
			throw new BusinessException(ResponseCodeEnum.CODE_600);
		}
		// 递归退出条件，一直查到根目录
		if (fileInfo.getFilePid().equals(rootFilePid)) {
			return;
		}
		// fileInfo.getFileId() 相当一层一层地往上找
		checkFilePid(rootFilePid, fileInfo.getFilePid(), userId);
	}

	@Override
	public void saveShare(String shareRootFilePid, String shareFileIds, String myFolderId, String shareUserId, String currentUserId) {
		String[] shareFileIdArray = shareFileIds.split(",");
		// 目标文件列表
		FileInfoQuery fileInfoQuery = new FileInfoQuery();
		fileInfoQuery.setUserId(currentUserId);
		fileInfoQuery.setFilePid(myFolderId);
		List<FileInfo> currentFileList = this.fileInfoMapper.selectList(fileInfoQuery);
		// 重命名
		Map<String, FileInfo> currentFileMap = currentFileList.stream().collect(Collectors.toMap(FileInfo::getFileName, Function.identity(), (data1, data2) -> data2));

		// 选择的文件
		fileInfoQuery = new FileInfoQuery();
		fileInfoQuery.setUserId(shareUserId);
		fileInfoQuery.setFileIdArray(shareFileIdArray);
		List<FileInfo> shareFileList = this.fileInfoMapper.selectList(fileInfoQuery);
		// 重命名选择的文件
		List<FileInfo> copyFileList = new ArrayList<>();
		Date curDate = new Date();
		for (FileInfo item : shareFileList) {
			FileInfo haveFile = currentFileMap.get(item.getFileName());
			if (haveFile != null) {
				item.setFileName(StringTools.rename(item.getFileName()));
			}
			findAllSubFile(copyFileList, item, shareUserId, currentUserId, curDate, myFolderId);
		}
		this.fileInfoMapper.insertBatch(copyFileList);
 	}

 	private void findAllSubFile(List<FileInfo> copyFileList, FileInfo fileInfo, String sourceUserId, String currentUserId, Date curDate, String newFilePid) {
		String sourceFileId = fileInfo.getFileId();
		fileInfo.setCreateTime(curDate);
		fileInfo.setLastUpdateTime(curDate);
		fileInfo.setFilePid(newFilePid);
		fileInfo.setUserId(currentUserId);
		String newFileId = StringTools.getRandomString(Constants.LENGTH_10);
		fileInfo.setFileId(newFileId);
		copyFileList.add(fileInfo);
		if (FileFolderTypeEnums.FOLDER.getType().equals(fileInfo.getFolderType())) {
			FileInfoQuery query = new FileInfoQuery();
			// 如果当前文件是目录类型，就把当前文件设成父级目录，去查它的子文件
			query.setFilePid(sourceFileId);
			query.setUserId(sourceUserId);
			List<FileInfo> sourceFileList = this.fileInfoMapper.selectList(query);
			for (FileInfo item : sourceFileList) {
				findAllSubFile(copyFileList, item, sourceUserId, currentUserId, curDate, newFilePid);
			}
		}
	}

}